﻿namespace ComTools.Attributes
{
    using System;
    using System.Net.Http;
    using System.Net.Http.Headers;
    using System.Web.Http.Filters;
    using System.Web.Http.Controllers;
    using System.Threading.Tasks;
    using Helpers;

    /// <summary>
    /// 处理完api请求后，对其响应头压缩
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    public class CompressResponseContentAttribute : ActionFilterAttribute
    {
        /// <summary>
        /// webapi调用日志
        /// </summary>
        //private WebApiMonitorLog log = null;

        /// <summary>
        /// Action执行前发生
        /// </summary>
        /// <param name="actionContext">上下文</param>
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            try
            {
                //log = NewSystemLog<WebApiMonitorLog>(log => log);
                //log.ActionName = actionContext.ActionDescriptor.ActionName;
                //log.ActionParams = actionContext.ActionArguments;
                //log.ControllerName = actionContext.ControllerContext.ControllerDescriptor.ControllerName;
                //log.ExecuteStartTime = DateTime.Now;
                //log.HttpMethod = actionContext.Request.Method.Method;
                //log.HttpRequestHeaders = actionContext.Request.Headers.ToString();
                //log.IP = GetRemoteIp(actionContext);
                //log.RequestPath = actionContext.Request.RequestUri.LocalPath;

            }
            catch (Exception)
            {
                throw new Exception("异步action调用前初始化api调用情况数据时出错");
            }

            base.OnActionExecuting(actionContext);
        }

        /// <summary>
        /// 获取远程IP
        /// </summary>
        /// <param name="actionContext">api上下文</param>
        /// <returns>远程IP</returns>
        private string GetRemoteIp(HttpActionContext actionContext)
        {
            var ret = "unknow";
            var valueObject = new object();

            if (actionContext.Request.Properties.TryGetValue("MS_OwinContext", out valueObject) &&
                actionContext.Request.Properties["MS_OwinContext"] is Microsoft.Owin.OwinContext)
            {
                var context = actionContext.Request.Properties["MS_OwinContext"] as Microsoft.Owin.OwinContext;
                ret = $"{context?.Request.RemoteIpAddress}:{context?.Request.RemotePort}";
            }
            else if (actionContext.Request.Properties.TryGetValue("MS_HttpContext", out valueObject) &&
                actionContext.Request.Properties["MS_HttpContext"] is System.Web.HttpContextWrapper)
            {
                var context = actionContext.Request.Properties["MS_HttpContext"] as System.Web.HttpContextWrapper;
                var address = context?.Request.Params.GetValues("REMOTE_ADDR")?[0] ?? "unknow";
                var port = context?.Request.Params.GetValues("REMOTE_PORT")?[0] ?? "unknow";
                ret = $"{address}:{port}";
            }
            return ret;
        }

        /// <summary>
        /// Action执行后发生
        /// </summary>
        /// <param name="actionExecutedContext">执行后的上下文</param>
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            if (actionExecutedContext.Exception == null)
            {
                try
                {
                    // 压缩数据
                    Deflate(actionExecutedContext);
                    //log.ExecuteEndTime = DateTime.Now;
                    //log.StatusCode = actionExecutedContext.Response.StatusCode.ToString();
                    //Task.Run(() => ApiLogger?.InsertData(log));
                }
                catch (Exception)
                {

                    throw new Exception("压缩响应头时出错");
                }
            }
            base.OnActionExecuted(actionExecutedContext);
        }

        /// <summary>
        /// 校验是否支持gzip和deflate压缩
        /// </summary>
        /// <param name="actionExecutedContext">action执行上下文</param>
        /// <returns>校验结果</returns>
        private bool IsSupportGzipAndDeflate(HttpActionExecutedContext actionExecutedContext)
        {
            return actionExecutedContext.Request.Headers.AcceptEncoding.Contains(new StringWithQualityHeaderValue("deflate")) ||
                actionExecutedContext.Request.Headers.AcceptEncoding.Contains(new StringWithQualityHeaderValue("gzip"));
        }

        /// <summary>
        /// 压缩响应头
        /// </summary>
        private void Deflate(HttpActionExecutedContext actionExecutedContext)
        {
            // 校验是否支持gzip和压缩
            if (!IsSupportGzipAndDeflate(actionExecutedContext)) return;

            // 获取响应上下文
            var content = actionExecutedContext.Response.Content;

            // 获取响应内容的byte数组
            var bytes = content == null ? null : content.ReadAsByteArrayAsync().Result;

            // 获取响应ContentType
            var contentType = content.Headers.ContentType.MediaType;

            // 获取响应的字符集
            var charSet = content.Headers.ContentType.CharSet ?? "utf-8";

            // 判读请求是否支持压缩，默认采用deflate标识的压缩
            if (actionExecutedContext.Request.Headers.AcceptEncoding.Contains(new StringWithQualityHeaderValue("deflate")))
            {
                var zlibbedContent = CompressionHelper.DeflateByte(bytes, Ionic.Zlib.CompressionLevel.BestSpeed);
                actionExecutedContext.Response.Content = new ByteArrayContent(zlibbedContent);
                actionExecutedContext.Response.Content.Headers.Add("Content-encoding", "deflate");
                actionExecutedContext.Response.Content.Headers.Add("Content-Type", contentType);
                actionExecutedContext.Response.Content.Headers.ContentType.CharSet = charSet;
            }
            else if (actionExecutedContext.Request.Headers.AcceptEncoding.Contains(new StringWithQualityHeaderValue("gzip")))
            {
                var zlibbedContent = CompressionHelper.GZipByte(bytes, Ionic.Zlib.CompressionLevel.BestSpeed);
                actionExecutedContext.Response.Content = new ByteArrayContent(zlibbedContent);
                actionExecutedContext.Response.Content.Headers.Add("Content-encoding", "gzip");
                actionExecutedContext.Response.Content.Headers.Add("Content-Type", contentType);
                actionExecutedContext.Response.Content.Headers.ContentType.CharSet = charSet;
            }
        }
    }
}
